package com.onpositive.realmview;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.onpositive.realmview.model.ColumnConfiguration;
import com.onpositive.realmview.model.GlobalChangeBroadcaster;
import com.onpositive.realmview.model.IArtifact;
import com.onpositive.realmview.model.IHasViews;
import com.onpositive.realmview.model.ILabel;
import com.onpositive.realmview.model.IModelObject;
import com.onpositive.realmview.model.IViewGrouping;
import com.onpositive.realmview.model.IWorkingCopy;
import com.onpositive.realmview.model.IWorkingCopyOwner;
import com.onpositive.realmview.model.filtering.IBooleanFilter;
import com.onpositive.realmview.model.filtering.IMayBeFilteredBySearch;
import com.onpositive.realmview.model.filtering.IUIFilter;
import com.onpositive.realmview.model.realm.IDRealm;
import com.onpositive.realmview.progress.IProgressMonitor;
import com.onpositive.realmview.search.GlobalSearch;
import com.onpositive.semantic.model.api.command.ICommand;
import com.onpositive.semantic.model.realm.FilteringRealm;
import com.onpositive.semantic.model.realm.HashDelta;
import com.onpositive.semantic.model.realm.IFilter;
import com.onpositive.semantic.model.realm.IModifiableRealm;
import com.onpositive.semantic.model.realm.IRealm;
import com.onpositive.semantic.model.realm.IRealmChangeListener;
import com.onpositive.semantic.model.realm.ISetDelta;
import com.onpositive.semantic.model.realm.ISimpleChangeListener;
import com.onpositive.semantic.model.realm.ITransformer;

public abstract class AbstractRealmView extends NamedObject implements IRealmView, IModelObject,
		IWorkingCopyOwner, IMayBeFilteredBySearch {

	public static final String DISCONNECTED_MODE_ID = "DISCONNECTED_MODE";

	public static final String SYNC_WITH_PRIVATE_SERVER_MODE_ID = "SYNC_WITH_PRIVATE_MODE";

//	private static final class ViewCopy extends WorkingCopy {
//
//		AbstractRealmView view;
//
//		private ViewCopy(IRichDocument original, AbstractRealmView view) {
//			super(original);
//			this.view = view;
//		}
//
//		public void changed() {
//			view.copies.put((IRichDocument) getOriginal(), this);
//			super.changed();
//		}
//
//		public IWorkingCopy clone() {
//			ViewCopy z = new ViewCopy(original, view);
//			z.fields.putAll(this.fields);
//			z.contents = this.contents;
//			return z;
//		}
//
//		public IWorkingCopyOwner getOwner() {
//			return view;
//		}
//	}

	private final class TotalFilters extends IDRealm<IUIFilter> implements
			IFilter, ISimpleChangeListener<IUIFilter> {

		protected IUIFilter lookup(String id) {
			return owner.getQueryProvider().getFilter(id);
		}

		boolean ignore;

		protected void fireDelta(ISetDelta<IUIFilter> dlt) {
			boolean hasChanges = false;
			for (IUIFilter f : dlt.getAddedElements()) {
				f.addFilterListener(this);
				hasChanges = true;
			}
			for (IUIFilter q : dlt.getRemovedElements()) {
				q.removeFilterListener(this);
				hasChanges = true;
			}
			super.fireDelta(dlt);
			if (hasChanges) {
				if (!ignore) {
					changed(null, null);
				}
			}
		}

		public boolean accept(Object element) {
			if (element instanceof IArtifact) {
				IArtifact c = (IArtifact) element;
				if (!GlobalSearch.accept(c)) {
					return false;
				}
			}
			for (IBooleanFilter f : filters) {
				if (!f.accept(element)) {
					return false;
				}
			}
			for (IFilter f : this) {
				if (!f.accept(element)) {
					return false;
				}
			}
			return true;
		}

		HashSet<ISimpleChangeListener<IFilter>> ls = new HashSet<ISimpleChangeListener<IFilter>>();

		@SuppressWarnings("unchecked")
		public void addFilterListener(
				ISimpleChangeListener<? extends IFilter> listener) {
			ls.add((ISimpleChangeListener<IFilter>) listener);
		}

		@SuppressWarnings("unchecked")
		public void removeFilterListener(
				ISimpleChangeListener<? extends IFilter> listener) {
			ls.remove((ISimpleChangeListener<IFilter>) listener);
		}

		public void changed(IUIFilter provider, Object extraData) {
			fireDelta(HashDelta.createChanged(provider));
			for (ISimpleChangeListener<IFilter> z : ls) {
				z.changed((IFilter) this, extraData);
			}
		}
	}

	protected IHasViews<?> owner;

	private HashMap<String, String> settings = new HashMap<String, String>();

	public String get(Object key) {
		return settings.get(key);
	}

	IDRealm<ILabel> labels = new IDRealm<ILabel>() {
	
		protected ILabel lookup(String id) {
			return owner.getQueryProvider().getLabel(id);
		}
	};

	IDRealm<ColumnConfiguration> columns = new IDRealm<ColumnConfiguration>() {
	
		protected ColumnConfiguration lookup(String id) {
			return owner.getQueryProvider().getColumn(id);
		}
	};

	IDRealm<ILabel> summaries = new IDRealm<ILabel>() {
	
		protected ILabel lookup(String id) {
			return owner.getQueryProvider().getSummary(id);
		}
	};

	IDRealm<IBooleanFilter> filters = new IDRealm<IBooleanFilter>() {
	
		protected IBooleanFilter lookup(String id) {
			return owner.getQueryProvider().getQuickFilter(id);
		}
	
		protected void fireDelta(ISetDelta<IBooleanFilter> dlt) {
			super.fireDelta(dlt);
			afilters.changed(null, null);
		}
	
	};

	PropertyChangeSupport s = new PropertyChangeSupport(this);

	TotalFilters afilters = new TotalFilters();

	IViewGrouping currentGrouping;

	IViewGrouping secondGrouping;

	private FilteringRealm<?> viewRealm;

	protected boolean storeCopiesLocally = false;

	protected boolean syncViewWithPrivateServer = false;
	
	protected HashMap<Object, IWorkingCopy> copies = new HashMap<Object, IWorkingCopy>();

//	private HashMap<IRichDocument, IRichDocumentWorkingCopy> copies = new HashMap<IRichDocument, IRichDocumentWorkingCopy>();
//
//	public IRichDocumentWorkingCopy copy(Object o) {
//		IRichDocumentWorkingCopy iRichDocumentWorkingCopy = copies.get(o);
//		if (iRichDocumentWorkingCopy != null) {
//			return iRichDocumentWorkingCopy;
//		}
//		if (o instanceof IRichDocumentWorkingCopy) {
//			return (IRichDocumentWorkingCopy) o;
//		}
//		IRichDocument z = (IRichDocument) o;
//		IRichDocumentWorkingCopy m = new ViewCopy(z, this);
//		return m;
//	}

	public String put(String key, String value) {
		String put = null;
		if (value == null) {
			put = settings.remove(key);
		} else {
			put = settings.put(key, value);
			save(null);
		}
		s.firePropertyChange(key, put, value);
		return put;
	}

	public AbstractRealmView clone() {
		AbstractRealmView v = createCopy();
		v.columns.add(columns());
		v.filters.add(usedQuickFilters());
		v.labels.add(labels());
		v.summaries.add(summaries());
		v.storeCopiesLocally = storeCopiesLocally;
		v.syncViewWithPrivateServer = syncViewWithPrivateServer;
		for (IUIFilter f : filters()) {
			v.afilters.add(f.clone());
		}
		v.currentGrouping = currentGrouping;
		v.secondGrouping = secondGrouping;
		v.description = description;
		v.icon = icon;
		v.name = name;
		v.settings = new HashMap<String, String>(this.settings);
		v.copies = cloneCopies(v);

		return v;
	}

	protected abstract HashMap<Object, IWorkingCopy> cloneCopies(IRealmView v);

	protected abstract AbstractRealmView createCopy();

	protected abstract void readCopies(DataInputStream stream);

	protected abstract void storeCopies(DataOutputStream outputStream) throws IOException;

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public AbstractRealmView(IModifiableRealm<? extends NamedObject> parent,
			IHasViews<?> hv) {
		super(parent);
		this.owner = hv;
		IRealmChangeListener listener = new IRealmChangeListener() {

			public void realmChanged(IRealm realmn, ISetDelta delta) {
				save(null);
			}
		};
		columns.addRealmChangeListener(listener);
		filters.addRealmChangeListener(listener);
	}

	@SuppressWarnings("unchecked")
	public AbstractRealmView(IHasViews<?> parent) {
		this((IModifiableRealm<? extends NamedObject>) parent.views(), parent);
	}

	public boolean isSyncViewWithPrivateServer() {
		return syncViewWithPrivateServer;
	}

	public void setSyncViewWithPrivateServer(boolean syncViewWithPrivateServer) {
		this.syncViewWithPrivateServer = syncViewWithPrivateServer;
	}

	public IModifiableRealm<IBooleanFilter> usedQuickFilters() {
		return filters;
	}

	public IModifiableRealm<ColumnConfiguration> columns() {
		return columns;
	}

	public IViewGrouping getCurrentGrouping() {
		return currentGrouping;
	}

	public void setCurrentGrouping(IViewGrouping grouping) {
		currentGrouping = grouping;
		save(null);
	}

	public IHasViews<?> getParent() {
		return owner;
	}

	public void read(DataInputStream stream) throws IOException {
		super.read(stream);
		String grouping = stream.readUTF();
		currentGrouping = owner.getQueryProvider().getGrouping(grouping);
		grouping = stream.readUTF();
		secondGrouping = owner.getQueryProvider().getGrouping(grouping);
		columns.load(stream);
		filters.load(stream);
		afilters.load(stream);
		labels.load(stream);
		summaries.load(stream);
		for (IUIFilter f : afilters) {
			f.readSettings(stream);
		}
		int k = stream.readInt();
		for (int a = 0; a < k; a++) {
			settings.put(stream.readUTF(), stream.readUTF());
		}
		String value = settings.get(DISCONNECTED_MODE_ID);

		if (storeCopiesLocally
				|| (value != null && value.equalsIgnoreCase("true"))) {
			readCopies(stream);
			storeCopiesLocally = true;
		}
		
		String v = settings.get(SYNC_WITH_PRIVATE_SERVER_MODE_ID);
		if(syncViewWithPrivateServer || (v != null && v.equalsIgnoreCase("true"))){
			syncViewWithPrivateServer = true;
		}
	}

	public void store(DataOutputStream stream) throws IOException {
		super.store(stream);
		stream.writeUTF(currentGrouping != null ? currentGrouping.getId() : "");
		stream.writeUTF(secondGrouping != null ? secondGrouping.getId() : "");
		columns.save(stream);
		filters.save(stream);
		afilters.save(stream);
		labels.save(stream);
		summaries.save(stream);
		for (IUIFilter f : afilters) {
			f.storeSettings(stream);
		}
		stream.writeInt(settings.size());
		for (String s : settings.keySet()) {
			stream.writeUTF(s);
			stream.writeUTF(settings.get(s));
		}
		if (storeCopiesLocally)
			storeCopies(stream);
	}

	public String toString() {
		return getName();
	}

	public Set<IModelObject> relatedObjects() {
		return Collections.emptySet();
	}

	public boolean delete() {
		if (owner.views().size() > 1) {
			owner.views().remove(this);
			return true;
		}
		return false;
	}

	@SuppressWarnings("unchecked")
	public void updateFiltersAndGrouping(IRealmView original) {

		HashDelta<?> buildFrom = HashDelta.buildFrom(this.afilters.getContents(),
				original.filters().getContents());
		HashDelta<?> ff = HashDelta.buildFrom(this.filters.getContents(), original
				.usedQuickFilters().getContents());

		this.currentGrouping = original.getCurrentGrouping();
		this.secondGrouping = original.getSecondLevelGrouping();

		// this.filters.set(original.usedQuickFilters());

		List<IUIFilter> ff1 = new ArrayList<IUIFilter>();
		ff1.addAll(this.afilters.getContents());
		this.afilters.remove(ff1);
		this.afilters.add(original.filters());

		// this.afilters.set(original.filters());
		afilters.changed(null, buildFrom);

		filters.applyDelta((ISetDelta<IBooleanFilter>) ff);
		save(null);
	}

	public void updateFrom(IRealmView original) {
		super.updateFrom(original);
		this.columns.set(original.columns());
		this.currentGrouping = original.getCurrentGrouping();
		this.secondGrouping = original.getSecondLevelGrouping();
		this.filters.set(original.usedQuickFilters());
		this.settings.clear();
		this.settings.putAll(original.getSettings());
		this.afilters.set(original.filters());
		this.labels.set(original.labels());
		this.summaries.set(original.summaries());
		if (original instanceof AbstractRealmView) {
			this.storeCopiesLocally = ((AbstractRealmView) original).storeCopiesLocally;
			if (((AbstractRealmView) original).storeCopiesLocally) {
				this.copies = new HashMap<Object, IWorkingCopy>(
						((AbstractRealmView) original).copies);
			} else
				this.copies.clear();

			this.syncViewWithPrivateServer = ((AbstractRealmView) original).syncViewWithPrivateServer;
			if (this.syncViewWithPrivateServer){
				this.copies = new HashMap<Object, IWorkingCopy>(
						((AbstractRealmView) original).copies);
			}
		}
		save(null);
	}

	public int hashCode() {
		return super.hashCode();
	}

	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (!super.equals(obj))
			return false;
		if (getClass() != obj.getClass())
			return false;
		AbstractRealmView other = (AbstractRealmView) obj;
		if (columns == null) {
			if (other.columns != null)
				return false;
		} else if (!columns.equals(other.columns))
			return false;
		if (labels == null) {
			if (other.labels != null)
				return false;
		} else if (!labels.equals(other.labels))
			return false;
		if (summaries == null) {
			if (other.summaries != null)
				return false;
		} else if (!summaries.equals(other.summaries))
			return false;
		if (currentGrouping == null) {
			if (other.currentGrouping != null)
				return false;
		} else if (!currentGrouping.equals(other.currentGrouping))
			return false;
		if (secondGrouping == null) {
			if (other.secondGrouping != null)
				return false;
		} else if (!secondGrouping.equals(other.secondGrouping))
			return false;
		if (filters == null) {
			if (other.filters != null)
				return false;
		} else if (!filters.equals(other.filters))
			return false;
		if (owner == null) {
			if (other.owner != null)
				return false;
		} else if (!owner.equals(other.owner))
			return false;
		if (!this.settings.equals(other.settings)) {
			return false;
		}
		if (afilters == null) {
			if (other.afilters != null)
				return false;
		} else if (!afilters.equals(other.afilters))
			return false;
		if (!copies.equals(other.copies)) { 
			return false;
		}
//		for (IRichDocument m : copies.keySet()) { //TODO equals by copies
//			WorkingCopy iRichDocumentWorkingCopy = (WorkingCopy) copies.get(m);
//			WorkingCopy iRichDocumentWorkingCopy2 = (WorkingCopy) other.copies
//					.get(m);
//			if (!iRichDocumentWorkingCopy.deapEquals(iRichDocumentWorkingCopy2)) {
//				return false;
//			}
//		}
		return true;
	}

	public Map<String, String> getSettings() {
		return settings;
	}

	public IModifiableRealm<IUIFilter> filters() {
		return afilters;
	}

	public void save(IProgressMonitor monitor) {

	}

	public abstract void commit(IProgressMonitor monitor,
			boolean locally);

	@SuppressWarnings("unchecked")
	public synchronized IRealm<?> getRealm() {
		if (viewRealm == null) {
			IRealm<?> viewRealm2 = owner.getViewRealm();
			IRealm<?> proxy = new ReplacingRealm((IRealm<?>) viewRealm2) {

				protected Object doReplace(Object next) {
					if (copies.containsKey(next)) {
						return copies.get(next);
					}
					return next;
				}

				protected Object replaceBack(Object next) {
					if (next instanceof IWorkingCopy) {
						IWorkingCopy m = (IWorkingCopy) next;
						return m.getOriginal();
					}
					return next;
				}

			};
			viewRealm = new FilteringRealm<Object>((IRealm<Object>) proxy,
					afilters) {

				ISimpleChangeListener<String> ls = new ISimpleChangeListener<String>() {

					public void changed(String provider, Object extraData) {
						afilters.changed(null, null);
					}
				};
				{
					GlobalSearch.addListener(ls);
				}

				public synchronized void realmChanged(IRealm<Object> realmn,
						ISetDelta<Object> delta) {
					HashDelta m = (HashDelta<?>) delta;
					m.transform(new ITransformer<Object, Object>() {

						public Object transform(Object object) {
							if (object instanceof IWorkingCopy) {
								IWorkingCopy m = (IWorkingCopy) object;
								if (!m.isDirty()) {
									filteredContents.remove(m);
									filteredContents.add(m.getOriginal());
									copies.remove(m.getOriginal());
								} else {
									boolean remove = filteredContents.remove(m
											.getOriginal());
									if (remove) {
										filteredContents.add(m);
									}
								}
							}
							return null;
						}
					});
					super.realmChanged(realmn, delta);
				}

				public void dispose() {
					synchronized (AbstractRealmView.this) {
						viewRealm = null;
					}
					GlobalSearch.removeListener(ls);
					super.dispose();
				}
			};
		}
		return viewRealm;
	}

	public Set<IUIFilter> removeStaleFilters() {
		afilters.ignore = true;
		HashSet<IUIFilter> fl = new HashSet<IUIFilter>();
		try {
			for (IUIFilter f : new HashSet<IUIFilter>(afilters.getContents())) {

				if (f.isStale()) {
					fl.add(f);
					afilters.remove(f);
				}
			}
		} finally {
			afilters.ignore = false;
			afilters.changed(null, null);
		}
		return fl;
	}

	public IViewGrouping getSecondLevelGrouping() {
		return secondGrouping;
	}

	public void setSecondLevelGrouping(IViewGrouping grouping) {
		this.secondGrouping = grouping;
		save(null);
	}

	public <T> T getAdapter(Class<T> adapter) {
		return getParent().getAdapter(adapter);
	}

	public void addSettingListener(PropertyChangeListener ls) {
		s.addPropertyChangeListener(ls);
	}

	public void removeSettingListener(PropertyChangeListener ls) {
		s.removePropertyChangeListener(ls);
	}

	public IModifiableRealm<ILabel> labels() {
		return labels;
	}

	public IModifiableRealm<ILabel> summaries() {
		return summaries;
	}

	@SuppressWarnings("unchecked")
	public void changed(ICommand m, Set<Object> objects) {
		if (viewRealm != null) {
			ISetDelta<Object> buildChanged = (ISetDelta) HashDelta.buildChanged(objects
					.toArray());
			((ReplacingRealm<Object>) viewRealm.getParent()).realmChanged(
					(IRealm<Object>) viewRealm, buildChanged);
			IRealmChangeListener[] listeners = GlobalChangeBroadcaster
					.getListeners();
			for (IRealmChangeListener l : listeners) {
				try {
					l.realmChanged((IRealm) viewRealm, buildChanged);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		} else {

			save(null);
			if (isStoreCopiesLocally()) {
				GlobalChangeBroadcaster.notifyChanged(this);
			}
		}
	}

	Object undocontext;

	public Object getUndoContext() {
		return undocontext;
	}

	public void setUndoContext(Object undocontext) {
		this.undocontext = undocontext;
	}

	public String getRealmDescription() {
		StringBuilder bld = new StringBuilder();

		return bld.toString();
	}

	public Object copyIfExists(Object lastSegment) {
		IWorkingCopy iRichDocumentWorkingCopy = copies
				.get(lastSegment);
		if (iRichDocumentWorkingCopy != null) {
			return iRichDocumentWorkingCopy;
		}
		return lastSegment;
	}

	public boolean canDelete() {
		return true;
	}

	/**
	 * @return the storeCopiesLocally
	 */
	public boolean isStoreCopiesLocally() {
		return storeCopiesLocally;
	}

	/**
	 * @param storeCopiesLocally
	 *            the storeCopiesLocally to set
	 */
	public void setStoreCopiesLocally(boolean storeCopiesLocally) {
		this.storeCopiesLocally = storeCopiesLocally;
	}

	public boolean isFiltered() {
		if (GlobalSearch.getPattern() != null
				&& GlobalSearch.getPattern().length() > 0) {
			if (this.viewRealm != null) {
				return viewRealm.size() == 0;
			}
			Object[] l = owner.getRealmContents();
			for (Object a : l) {
				if (afilters.accept(a)) {
					return false;
				}
			}
			return true;
		}
		return false;
	}

	public Object[] getContents() {
		if (viewRealm != null) {
			return viewRealm.getContents().toArray();
		}
		ArrayList<Object> rs = new ArrayList<Object>();
		Object[] l = owner.getRealmContents();
		for (Object a : l) {
			if (copies.containsKey(a)) {
				a = copies.get(a);
			}
			if (afilters.accept(a)) {

				rs.add(a);
			}
		}

		return rs.toArray();
	}
}
